﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;

namespace Common.Infrastructure
{
    public static class ApiGroupParamExtensions
    {
        private static readonly Dictionary<ApiRuleOperateEnum, Func<Expression, Expression, Expression>> ExpressionDict;

        static ApiGroupParamExtensions()
        {
            Dictionary<ApiRuleOperateEnum, Func<Expression, Expression, Expression>> dictionary = new Dictionary<ApiRuleOperateEnum, Func<Expression, Expression, Expression>>
            {
                { ApiRuleOperateEnum.等于, new Func<Expression, Expression, Expression>(Expression.Equal) },
                { ApiRuleOperateEnum.不等于, new Func<Expression, Expression, Expression>(Expression.NotEqual) },
                { ApiRuleOperateEnum.小于, new Func<Expression, Expression, Expression>(Expression.LessThan) },
                { ApiRuleOperateEnum.小于等于, new Func<Expression, Expression, Expression>(Expression.LessThanOrEqual) },
                { ApiRuleOperateEnum.大于, new Func<Expression, Expression, Expression>(Expression.GreaterThan) },
                { ApiRuleOperateEnum.大于等于, new Func<Expression, Expression, Expression>(Expression.GreaterThanOrEqual) },
                {
                    ApiRuleOperateEnum.包含, (left, right) =>
                    {
                        if (left.Type != typeof(string))
                        {
                            throw new NotSupportedException("Contains Only Support String Type");
                        }

                        return Expression.Call(left, typeof(string).GetMethod("Contains", new Type[] { typeof(string), }), new Expression[] { right, });
                    }
                },
                {
                    ApiRuleOperateEnum.InByString, (left, right) =>
                    {
                        if (right.Type != typeof(List<string>) || left.Type != typeof(string))
                        {
                            throw new NotSupportedException("InByString Only Support String Type");
                        }

                        return Expression.Call(right, typeof(List<string>).GetMethod("Contains", new Type[] { typeof(string), }), new Expression[] { left, });
                    }
                },
                {
                    ApiRuleOperateEnum.InByInt, (left, right) =>
                    {
                        if (right.Type != typeof(List<int>) || left.Type != typeof(int))
                        {
                            throw new NotSupportedException("InByInt Only Support Int Type");
                        }

                        return Expression.Call(right, typeof(List<int>).GetMethod("Contains", new Type[] { typeof(int), }), new Expression[] { left, });
                    }
                },
                {
                    ApiRuleOperateEnum.前包含, (left, right) =>
                    {
                        if (right.Type != typeof(string) || left.Type != typeof(string))
                        {
                            throw new NotSupportedException("StartsWith Only Support string Type");
                        }

                        return Expression.Call(left, typeof(string).GetMethod("StartsWith", new Type[] { typeof(string), }), new Expression[] { right, });
                    }
                },
                {
                    ApiRuleOperateEnum.后包含, (left, right) =>
                    {
                        if (right.Type != typeof(string) || left.Type != typeof(string))
                        {
                            throw new NotSupportedException("EndsWith Only Support string Type");
                        }

                        return Expression.Call(left, typeof(string).GetMethod("EndsWith", new Type[] { typeof(string), }), new Expression[] { right, });
                    }
                }
            };
            ApiGroupParamExtensions.ExpressionDict = dictionary;
        }

        /// <summary>
        /// 获取指定查询条件组的查询表达式.
        /// </summary>
        /// <typeparam name="T">表达式实体类型.</typeparam>
        /// <param name="group">查询条件组，如果为null，则直接返回 true 表达式.</param>
        /// <returns>查询表达式.</returns>
        public static Expression<Func<T, bool>> GetExpression<T>(this ApiGroupParam group)
        {
            ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "m");
            Expression expressionBody = ApiGroupParamExtensions.GetExpressionBody(parameterExpression, group);
            return Expression.Lambda<Func<T, bool>>(expressionBody, new ParameterExpression[] { parameterExpression });
        }

        /// <summary>
        /// 获取指定查询条件的查询表达式.
        /// </summary>
        /// <typeparam name="T">表达式实体类型.</typeparam>
        /// <param name="rule">查询条件，如果为null，则直接返回 true 表达式.</param>
        /// <returns>查询表达式.</returns>
        public static Expression<Func<T, bool>> GetExpression<T>(this ApiRuleParam rule)
        {
            ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "m");
            Expression expressionBody = ApiGroupParamExtensions.GetExpressionBody(parameterExpression, rule);
            return Expression.Lambda<Func<T, bool>>(expressionBody, new ParameterExpression[] { parameterExpression });
        }

        private static Expression GetExpressionBody(ParameterExpression param, ApiGroupParam group)
        {
            Expression result;
            if (group == null || (group.Rules.Count == 0 && group.Groups.Count == 0))
            {
                result = Expression.Constant(true);
            }
            else
            {
                List<Expression> list = new List<Expression>();
                list.AddRange(group.Rules.Select(t => ApiGroupParamExtensions.GetExpressionBody(param, t)).ToList());
                list.AddRange(group.Groups.Select(t => ApiGroupParamExtensions.GetExpressionBody(param, t)).ToList());
                result = group.IsAnd ?
                    list.Aggregate(new Func<Expression, Expression, Expression>(Expression.AndAlso)) :
                    list.Aggregate(new Func<Expression, Expression, Expression>(Expression.OrElse));
            }

            return result;
        }

        private static Expression GetExpressionBody(ParameterExpression param, ApiRuleParam rule)
        {
            LambdaExpression propertyLambdaExpression = ApiGroupParamExtensions.GetPropertyLambdaExpression(param, rule);
            Expression arg = ApiGroupParamExtensions.ChangeTypeToExpression(rule, propertyLambdaExpression.Body.Type);
            return ApiGroupParamExtensions.ExpressionDict[rule.Operate](propertyLambdaExpression.Body, arg);
        }

        private static LambdaExpression GetPropertyLambdaExpression(ParameterExpression param, ApiRuleParam rule)
        {
            string[] array = rule.Field.Split(new char[]
            {
                '.',
            });
            Expression expression = param;
            Type type = param.Type;
            string[] array2 = array;
            for (int i = 0; i < array2.Length; i++)
            {
                string name = array2[i];
                PropertyInfo property = type.GetProperty(name);
                if (property != null)
                {
                    type = property.PropertyType;
                    expression = Expression.MakeMemberAccess(expression, property);
                }
            }

            return Expression.Lambda(expression, new ParameterExpression[] { param });
        }

        private static Expression ChangeTypeToExpression(ApiRuleParam rule, Type conversionType)
        {
            Type typeUnNull = conversionType.GetUnNullableType();
            object value = null;
            string text = typeUnNull.Name.ToUpper();
            if (rule.Operate == ApiRuleOperateEnum.InByInt)
            {
                if (rule.Value != null && !string.IsNullOrEmpty(rule.Value))
                {
                    value = rule.Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(t => LibSysUtils.ToInt(t)).ToList();
                }
                else
                {
                    value = new List<int>();
                }

                conversionType = typeof(List<int>);
            }
            else if (rule.Operate == ApiRuleOperateEnum.InByString)
            {
                if (rule.Value != null && !string.IsNullOrEmpty(rule.Value))
                {
                    value = rule.Value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                }
                else
                {
                    value = new List<string>();
                }

                conversionType = typeof(List<string>);
            }
            else if (text != null && string.Compare(text, "string", true) == 0)
            {
                if (rule.Value != null && !string.IsNullOrEmpty(rule.Value))
                {
                    value = rule.Value;
                }
            }
            else if (!string.IsNullOrEmpty(text) && string.Compare(text, "boolean", true) == 0)
            {
                value = LibSysUtils.ToBoolean(rule.Value);
            }
            else
            {
                value = ConvertHelper.ChangeType(rule.Value, typeUnNull);
            }

            return Expression.Constant(value, conversionType);
        }

        private static class ConvertHelper
        {
            public static object ChangeType(object obj, Type conversionType)
            {
                return ApiGroupParamExtensions.ConvertHelper.ChangeType(obj, conversionType, Thread.CurrentThread.CurrentCulture);
            }

            public static object ChangeType(object obj, Type conversionType, IFormatProvider provider)
            {
                Type underlyingType = Nullable.GetUnderlyingType(conversionType);
                object result;
                if (underlyingType != null)
                {
                    if (obj == null || string.IsNullOrEmpty(obj?.ToString()))
                    {
                        result = null;
                    }
                    else
                    {
                        result = Convert.ChangeType(obj, underlyingType, provider);
                    }
                }
                else if (typeof(Enum).IsAssignableFrom(conversionType))
                {
                    result = Enum.Parse(conversionType, obj.ToString());
                }
                else
                {
                    result = Convert.ChangeType(obj, conversionType, provider);
                }

                return result;
            }
        }
    }
}
